﻿

$$.Tpl = function (page) {
	//tpl：false 不处理/none 删除/sub 子模板/其它 正常模板
	//tpl-alt：模板解析后显示内容，默认是空node
	//tpl-class：追加的class
	//tpl-style：追加的css
	//tpl-img：加载的图片、背景
	//tpl-var：输出初始化变量，user=$data.User;多个用分号分隔
	//tpl-no：不输出判断语句，true时不输出
    //tpl-if：输出判断语句，true时输出
    //tpl-link：打开外部链接，执行$$.Shell.OpenLink(href,title)，可在模板外；title：data-title
    //tpl-href：打开页面，执行$$.Page.Open(href,callback)，可在模板外；callback：data-callback
    //tpl-evt：事件绑定，可在模板外；事件名[,事件名]:方法名/#语句；事件名以!开头表示只触发一次；模板外解析时绑定，内容输出后绑定；	click;tap	click:show	click:#show(3)	click,tap:show
	//tpl-click：点击事件绑定，tpl-evt=click:xxx的简写
	//公共参数：$data 传入数据；$page 所在页面；$node 模板节点；$tpl 模板处理器；
	//模板输出完成后产生tplafter事件

	//待实现：模板外部事件绑定
    //tpl-fail：（暂未实现）模板输出失败后显示内容
    //tpl-succ：（暂未实现）模板输出成功后执行方法名，#开始表示是语句，可多个
	var me = this, Tpls = {}, Roots = [], Evts = {}, Index = 1;
	var emptyData = { NONE: true };

	function escapeH(js) {
		if(!js) return js;
	    return js.replace(/&amp;/g, "&")
            .replace(/&lt;/g, "<")
            .replace(/&gt;/g, ">");
	}
	function parseVar(node) {
		var vars = node.getAttribute("tpl-var");
		if (!vars) return "";
		node.removeAttribute("tpl-var");
		var arr = vars.match(/[^\s;][^;]*=[^;]*[^\s;]/g);
		vars = "";
		for (var i in arr) { vars += "var " + arr[i] + "||{};"; }
		if (!vars) return "";
		var js = "try{" + escapeH(vars) + "}catch(e){console.warn(e);return;}";
		return js;
	}
	function parseNo(node) {
		var no = node.getAttribute("tpl-no");
		if (!no) return "";
		delete node.removeAttribute("tpl-no");
		var js = "if(" + escapeH(no) + ") return;";
		return js;
	}
	function parseIf(node) {
		var ifs = node.getAttribute("tpl-if");
		if (!ifs) return "";
		delete node.removeAttribute("tpl-if");
		var js = "if(!(" + escapeH(ifs) + ")) return;";
		return js;
	}

	//function parseInit(node) {
	//	if (!node.dataset.init) return "";
	//	var arr = node.dataset.init.match(/[^\s;][^;]*=[^;]*[^\s;]/g)
	//	delete node.dataset.init;
	//	var js = "";
	//	for (var i in arr) { js += arr[i] + "(node);"; }
	//	return js;
	//}
	function dealAlt(node) {
		var alt = node.getAttribute("tpl-alt");
		node.removeAttribute("tpl-alt");
		if (!alt) return;
		node.innerHTML = alt || "";
	}

	function parseEvts(pnode) {
		function parse(node) {
			var str = node.getAttribute("tpl-evt");
			node.removeAttribute("tpl-evt");
			if (!str) return;
			var arr = str.split(";");
			if (!arr || arr.length < 1) return;
			var bodys = [];
			for (var i in arr) {
				var s = arr[i];
				if (!s || s.length < 4) continue;
				var ar = s.split(":");
				if (!ar || ar.length < 2) continue;
				var ns = ar[0].trim().split(","); if (!ns) continue;
				var h = ar[1].trim(); if (!h) continue;
				if (h.charAt(0) === "#") {
					h = h.substring(1).trim();
					if (!h) continue;
					h = "function(evt){" + h.replace(/&amp;/g, "&") + "}";
				}
				for (var j in ns) {
					var n = ns[j].trim();
					if (!n) continue;
					s = "";
					if (n.charAt(0) === "!") {
						n = n.substr(1).trim();
						if (!n) continue;
						s = "$(this).one";
					}
					else s = "$(this).on";
					if (n === "click") bodys.push(s + "Click(");
					else bodys.push(s + "('" + n + "',");
					bodys.push(h);
					bodys.push(");");
				}
			}
			var body = bodys.join("\r\n"); 
			var func = null;
			try { 
				func = new Function("$node","$page","$tpl", body);
			}
			catch (e) {
				console.info(body);
				console.error(e.message); return;
			}
			var id = Index++;
			Evts["" + id] = func;
			node.setAttribute("tpl-eid", id);
		}

		function parseclk(node) {
			var h = node.getAttribute("tpl-click");
			node.removeAttribute("tpl-click");
			if (!h) return;
			if (h.charAt(0) === "#") {
				h = h.substring(1).trim();
				if (!h) return;
				h = "function(evt){" + h.replace(/&amp;/g, "&") + "}";
			}
			var bodys = [];
			bodys.push("$(this).onClick(");
			bodys.push(h);
			bodys.push(");");
			var body = bodys.join("\r\n"); 
			var func = null;
			try { 
				func = new Function("$node","$page","$tpl", body);
			}
			catch (e) {
				console.info(body);
				console.error(e.message); return;
			}
			var id = Index++;
			Evts["" + id] = func;
			node.setAttribute("tpl-eid", id);
		}

		if (pnode.hasAttribute("tpl-click")) parseclk(pnode);
		var arr = pnode.querySelectorAll("[tpl-click]");
		for (var i = 0; i < arr.length; i++) { parseclk(arr[i]); }

		if (pnode.hasAttribute("tpl-evt")) parse(pnode);
		var arr = pnode.querySelectorAll("[tpl-evt]");
		for (var i = 0; i < arr.length; i++) { parse(arr[i]); }
	}
	function bindEvt(node, $node) {
		var id = node.getAttribute("tpl-eid");
		node.removeAttribute("tpl-eid");
		if (!id) return;
		var func = Evts[id];
		if (func) func.call(node, $node, page, me);
	}

	function bindHref(node) {
	    var href = node.getAttribute("tpl-href");
	    node.removeAttribute("tpl-href");
	    if (!href) return;
	    var cbk = node.getAttribute("data-callback") || "null";
		var bodys = [];
		bodys.push("var $page=this.$page;");
		bodys.push("var href='");
		bodys.push(escapeH(href));
		bodys.push("';var cbk=");
		bodys.push(cbk);
		bodys.push(";\r\n$$.Page.OpenHref(href, cbk);");
		var func = null;
		var body = bodys.join(""); 
		try { 
			func = new Function(body);
			func = func.bind(env);
		}
		catch (e) {
			console.info(body);
			console.error(e.message); 
			return;
		}
	    $(node).onClick(func);
	}
	function bindLink(node) {
		var href = node.getAttribute("tpl-link");
		node.removeAttribute("tpl-link");
		if (!href) return;
		href = escapeH(href);
		var title = node.getAttribute("data-title") || null;
		$(node).onClick(function () {
			$$.Shell.OpenLink(href, title);
		});
	}
	function bindClass(node) {
		var cls = node.getAttribute("tpl-class");
		node.removeAttribute("tpl-class");
		if (!cls) return;
		cls = cls.trim();
		if (!cls) return;
		node.classList.add(cls);
	}
	function bindStyle(node) {
		var css = node.getAttribute("tpl-style");
		node.removeAttribute("tpl-style");
		if (!css) return;
		css = css.trim();
		if (!css) return;
		node.style.cssText += css;
	}
	function bindImg(node){
		var src = node.getAttribute("tpl-img");
        node.removeAttribute("tpl-img");
		if(!src) return;
        var im = new Image();
        im.onload = function () {
            if ((node.tagName || node.nodeName || "").toLowerCase() === "img") node.setAttribute("src", im.src);
            else node.style.backgroundImage = "url(" + im.src + ")";
        };
        im.src = src;
	}

	var env = {
		$page : page,
		$tpl : me,
		bindEvt : bindEvt,
		bindHref : bindHref,
		bindLink : bindLink,
		bindClass : bindClass,
		bindStyle : bindStyle,
		bindImg : bindImg
	};

	function parseHtml(html) {
		if (!html) return "";
		var arr = [];
		arr.push("var arr$_=[],s$_='';");
		arr.push("function render$_(){");
		arr.push("var htm$_=arr$_.join('').trim();arr$_=[];if(!htm$_)return;");
		arr.push("var div$_=document.createElement('div');div$_.innerHTML=htm$_;");
		arr.push("var arr1$_=[];for (var i$_=0;i$_<div$_.childNodes.length;i$_++) { arr1$_.push(div$_.childNodes[i$_]); }");
		arr.push("for (var i$_=0;i$_<arr1$_.length;i$_++) { docfrag$_.appendChild(arr1$_[i$_]); }");
		arr.push("}")

		var idx = -1, lidx = 0, len = html.length, c = null;
		while (++idx < len) {
			c = html.charAt(idx);
			if (c === '<') Js();
			else if (c === '{') Var();
		}
		if (lidx < len) {
			$.each(html.substring(lidx, idx).split(/[\r\n]/g), function (i, s) {
				if (s = $.trim(s)) arr.push("arr$_.push('" + s.replace(/'/g, "\\'") + "');");
			});
		}
		function Js() {
			if (idx + 3 >= len) return;
			if (html.charAt(idx + 1) !== '!') return;
			if (html.charAt(idx + 2) !== '-') return;
			if (html.charAt(idx + 3) !== '-') return;
			if (lidx < idx) $.each(html.substring(lidx, idx).split(/[\r\n]/g), function (i, s) {
				if (s = $.trim(s)) arr.push("arr$_.push('" + s.replace(/'/g, "\\'") + "');");
			});
			lidx = idx;
			//if (html.charAt(idx + 4) == '{') { idx += 4; sub(); return; }
			var tidx = idx + 3;
			while (++tidx < len) {
				if (html.charAt(tidx) !== '-') continue;
				if (tidx + 2 >= len) return;
				if (html.charAt(tidx + 1) !== '-') continue;
				if (html.charAt(tidx + 2) !== '>') continue;
				lidx = tidx + 3;
				break;
			}
			if (lidx <= idx) return;

			idx += 4;
			if (tidx > idx && html.charAt(idx) === '#') {
				var s = $.trim(html.substring(idx + 1, tidx));
				if (s) arr.push(s);
			}
			idx = tidx + 2;
		}
		function Var() {
			if (lidx < idx) {
				$.each(html.substring(lidx, idx).split(/[\r\n]/g), function (i, s) {
					if (s = $.trim(s)) arr.push("arr$_.push('" + s.replace(/'/g, "\\'") + "');");
				});
			}
			lidx = idx;
			var tidx = idx, has = false;
			while (++tidx < len) {
				if (html.charAt(tidx) !== '}') continue;
				if (html.charAt(tidx - 1) === '\\') { has = true; continue; }
				lidx = tidx + 1;
				break;
			}
			if (lidx <= idx) return;

			idx += 1;
			if (tidx > idx) {
				var s = $.trim(html.substring(idx, tidx));
				if (s) {
					if (has) s = s.replace(/\\}/g, "}");
					arr.push("try{s$_=(" + escapeH(s) + ");}catch(e_){s$_='';}if(s$_===undefined||s$_===null)s$_='';arr$_.push(s$_);");
				}
			}
			idx = tidx;
		}

		arr.push("render$_();");
		return arr.join("\r\n");
	}

	function parseNode(node) {
		if (!node) return null;
		if (!node.parentNode) return null;
		var tpl = (""+node.getAttribute("tpl")).trim().toLowerCase();
		node.removeAttribute("tpl");
		if (tpl === "false") return null;
		var $node = $(node);
		if (tpl === "none") { $node.remove(); return null; }
		bindHref(node);
		bindLink(node);

		function parse(sub) {
			if (!sub) bindEvt(node,node);
			var bodys = [];
			bodys.push("var $page=this.$page,$tpl=this.$tpl;");
			bodys.push("var bindEvt=this.bindEvt,bindHref=this.bindHref,bindLink=this.bindLink;");
			bodys.push("var bindClass=this.bindClass,bindStyle=this.bindStyle,bindImg=this.bindImg;");
			var js = parseVar(node); if (js) bodys.push(js);
			js = parseNo(node); if (js) bodys.push(js);
			js = parseIf(node); if (js) bodys.push(js);
			bodys.push("var docfrag$_=document.createDocumentFragment();");
		    //var inits_ = parseInit(node);
			//var loop = parseFor(node); if (loop) arr.push(loop);
			js = parseHtml(node.innerHTML); if (js) bodys.push(js);
			//if (loop) arr.push("}");
			bodys.push("if(docfrag$_.childNodes.length<1) return;");
			bodys.push("var arr2$_=docfrag$_.querySelectorAll('[tpl-id]');");
			bodys.push("for (var i$_=arr2$_.length-1;i$_>=0;i$_--){$tpl.RenderNode(arr2$_[i$_],$data);}");
			bodys.push("$node.appendChild(docfrag$_);");
			bodys.push("arr2$_=$node.querySelectorAll('[tpl-class]');");
			bodys.push("for (var i$_=0;i$_<arr2$_.length;i$_++){bindClass(arr2$_[i$_]);}");
			bodys.push("arr2$_=$node.querySelectorAll('[tpl-style]');");
			bodys.push("for (var i$_=0;i$_<arr2$_.length;i$_++){bindStyle(arr2$_[i$_]);}");
			bodys.push("arr2$_=$node.querySelectorAll('[tpl-img]');");
			bodys.push("for (var i$_=0;i$_<arr2$_.length;i$_++){bindImg(arr2$_[i$_]);}");
			bodys.push("arr2$_=$node.querySelectorAll('[tpl-eid]');");
			bodys.push("for (var i$_=0;i$_<arr2$_.length;i$_++){bindEvt(arr2$_[i$_],$node);}");
			bodys.push("arr2$_=$node.querySelectorAll('[tpl-href]');");
			bodys.push("for (var i$_=0;i$_<arr2$_.length;i$_++){bindHref(arr2$_[i$_]);}");
			bodys.push("arr2$_=$node.querySelectorAll('[tpl-link]');");
			bodys.push("for (var i$_=0;i$_<arr2$_.length;i$_++){bindLink(arr2$_[i$_]);}");
			bodys.push("$($node).triggerHandler('tplafter');");
			
			var func = null;
			var body = bodys.join("\r\n"); 
			try { 
				func = new Function("$data","$node", body);
				func = func.bind(env);
			}
			catch (e) {
				console.info(body);
				console.error(e.message); return;
			}

			$node.empty();
			dealAlt(node);

			if (!func) return;
			var id = "" + Index++;
			node.setAttribute("tpl-id", id);
			Tpls[id] = func;
			if (!sub) Roots.push(node);
		}
		parse(tpl === "sub");
		return node;
	}

	me.RenderNode = function (node, data, pageok) {
		if (!node) return;
		if(node instanceof jQuery) node = node[0];
		var id = node.getAttribute("tpl-id");
		if (!id) return;
		var func = Tpls[id];
		if (!func) return;
		$(node).empty();
	    try { func(data || emptyData, node); }
		catch (e) {
			console.info(func);
			console.error(e.message);
		}
	    if (pageok === true) page.RenderOk(node);
	};
	me.AppendNode = function (node, data, pageok) {
		if (!node) return;
		if(node instanceof jQuery) node = node[0];
		var id = node.getAttribute("tpl-id");
		if (!id) return;
		var func = Tpls[id];
		if (!func) return;
		try { func(data || emptyData, node); }
		catch (e) { console.error(e.message); }
		if (pageok === true) page.RenderOk(node);
	};
	me.RenderPage = function (data, pageok) {
		if (Roots.length < 1) { page.RenderOk(); return; }
		for (var i = 0, len = Roots.length; i < len; i++) {
			var node = Roots[i];
			if (!node) continue;
			var id = node.getAttribute("tpl-id");
			if (!id) continue;
			var func = Tpls[id];
			if (!func) continue;
			$(node).empty();
			try { func(data || emptyData, node); }
		    catch (e) { console.info(func); console.error(e.message); }
		}
		if (pageok !== false) page.RenderOk();
	};

	if (page && page.Node) {
		var pgnod = page.Node;
		if(pgnod instanceof jQuery) pgnod = pgnod[0];
	    parseEvts(pgnod);
		var arr = pgnod.querySelectorAll("[tpl]");
		for (var i = arr.length - 1; i >= 0; i--) { parseNode(arr[i]); }
		if (pgnod.hasAttribute("tpl")) parseNode(pgnod);
		arr = pgnod.querySelectorAll("[tpl-href]");
		for (var i = arr.length - 1; i >= 0; i--) { bindHref(arr[i]); }
		arr = pgnod.querySelectorAll("[tpl-link]");
		for (var i = arr.length - 1; i >= 0; i--) { bindLink(arr[i]); }
		arr = pgnod.querySelectorAll("[tpl-class]");
		for (var i = arr.length - 1; i >= 0; i--) { bindClass(arr[i]); }
		arr = pgnod.querySelectorAll("[tpl-style]");
		for (var i = arr.length - 1; i >= 0; i--) { bindStyle(arr[i]); }
		arr = pgnod.querySelectorAll("[tpl-img]");
		for (var i = arr.length - 1; i >= 0; i--) { bindImg(arr[i]); }
		arr = pgnod.querySelectorAll("[tpl-eid]");
		for (var i = arr.length - 1; i >= 0; i--) {
		    var n = arr[i];
		    if (!n.hasAttribute("tpl-id")) bindEvt(n);
		}
		delete parseVar;
		delete parseNo;
		delete parseIf;
		delete parseEvts;
		delete parseHtml;
		delete parseNode;
	}
};

$$.Tpl.Empty = new function () {
	function empty() { };
	this.RenderNode = empty;
	this.AppendNode = empty;
	this.RenderPage = empty;
};